<?php

namespace app\install\controller;

use support\Request;
use support\Response;
use Illuminate\Support\Str;

/**
 * Web安装引导控制器
 */
class Install
{
    /**
     * 安装首页 - 直接显示安装表单
     */
    public function index(Request $request): Response
    {
        // 检查是否已安装
        if ($this->isInstalled()) {
            return $this->redirectToInstalled();
        }

        // 环境检测 - 只显示不通过的项目
        $failedChecks = $this->getFailedChecks();

        return view('install/index', [
            'title'        => '系统安装',
            'failedChecks' => $failedChecks,
            'hasErrors'    => !empty($failedChecks)
        ]);
    }

    /**
     * 配置页面
     */
    public function config(Request $request): Response
    {
        if ($request->method() === 'GET') {
            // 检查是否已安装（GET请求时检查）
            if ($this->isInstalled()) {
                return $this->redirectToInstalled();
            }

            return view('install/config', [
                'title' => '系统安装 - 配置信息'
            ]);
        }

        // 处理POST请求 - 保存配置（允许覆盖安装）
        try {
            $data = $request->post();

            // 验证必填字段
            $required = ['db_host', 'db_name', 'db_user', 'db_password', 'admin_username', 'admin_password'];
            foreach ($required as $field) {
                if (empty($data[$field])) {
                    throw new \Exception("字段 {$field} 不能为空");
                }
            }

            // 测试数据库连接
            $this->testDatabaseConnection($data);

            // 保存配置到session
            session()->put('install_config', $data);

            return json(['code' => 0, 'msg' => '配置保存成功']);
        } catch (\Exception $e) {
            return json(['code' => 1, 'msg' => $e->getMessage()]);
        }
    }

    /**
     * 安装处理
     */
    public function install(Request $request): Response
    {
        try {
            // 先尝试从session获取配置
            $config = session()->get('install_config');

            // 如果session中没有配置，尝试从POST请求中获取
            if (!$config) {
                $postData = $request->post();
                if (!empty($postData['db_host']) && !empty($postData['db_name']) && !empty($postData['admin_username'])) {
                    $config = $postData;
                    // 保存到session以备后用
                    session()->put('install_config', $config);
                } else {
                    throw new \Exception('配置信息丢失，请重新填写表单');
                }
            }

            // 验证必要的配置项
            $required = ['db_host', 'db_name', 'db_user', 'admin_username', 'admin_password'];
            foreach ($required as $field) {
                if (empty($config[$field])) {
                    throw new \Exception("配置项 {$field} 缺失，请重新配置");
                }
            }

            // 如果是覆盖安装，先删除.lock文件
            $lockFile = base_path() . '/.lock';
            if (file_exists($lockFile)) {
                unlink($lockFile);
            }

            // 执行安装步骤
            $this->performInstallation($config, $request);

            // 创建.lock文件
            $this->createLockFile();

            // 生成一次性访问令牌
            $token = $this->generateCompleteToken();

            // 清除session
            session()->forget('install_config');

            // 自动重载服务
            $this->reloadService();

            return json(['code' => 0, 'msg' => '安装成功', 'token' => $token]);
        } catch (\Exception $e) {
            return json(['code' => 1, 'msg' => $e->getMessage()]);
        }
    }

    /**
     * 测试数据库连接
     */
    public function testConnection(Request $request): Response
    {
        try {
            $data = $request->post();

            // 验证必填字段
            $required = ['db_host', 'db_name', 'db_user'];
            foreach ($required as $field) {
                if (empty($data[$field])) {
                    return json(['code' => 1, 'msg' => "字段 {$field} 不能为空"]);
                }
            }

            // 测试数据库连接
            $connectionResult = $this->testDatabaseConnection($data);

            return json([
                'code'       => 0,
                'msg'        => '数据库连接成功',
                'hasData'    => $connectionResult['hasData'],
                'tableCount' => $connectionResult['tableCount']
            ]);
        } catch (\Exception $e) {
            return json(['code' => 1, 'msg' => $e->getMessage()]);
        }
    }

    /**
     * 安装完成页面 - 需要一次性令牌
     */
    public function complete(Request $request): Response
    {
        if (!$this->isInstalled()) {
            return redirect('/install');
        }

        // 验证一次性令牌
        $token = $request->get('token');
        if (!$this->validateCompleteToken($token)) {
            return $this->redirectToInstalled();
        }

        // 消费令牌（删除）
        $this->consumeCompleteToken($token);

        return view('install/complete', [
            'title'     => '安装完成',
            'admin_url' => '/admin/login'
        ]);
    }

    /**
     * 检查是否已安装
     */
    private function isInstalled(): bool
    {
        return file_exists(base_path() . '/.lock');
    }

    /**
     * 获取不通过的环境检测项目
     */
    private function getFailedChecks(): array
    {
        $allChecks = $this->checkEnvironment();
        return array_filter($allChecks, function ($check) {
            return !$check['status'];
        });
    }

    /**
     * 环境检测
     */
    private function checkEnvironment(): array
    {
        $checks = [];

        // PHP版本检测
        $checks[] = [
            'name'     => 'PHP版本',
            'required' => '>= 8.2',
            'current'  => PHP_VERSION,
            'status'   => version_compare(PHP_VERSION, '8.2', '>=')
        ];

        // 扩展检测
        $extensions = ['pdo', 'pdo_mysql', 'json', 'mbstring', 'openssl'];
        foreach ($extensions as $ext) {
            $checks[] = [
                'name'     => "PHP扩展: {$ext}",
                'required' => '已安装',
                'current'  => extension_loaded($ext) ? '已安装' : '未安装',
                'status'   => extension_loaded($ext)
            ];
        }

        // 目录权限检测
        $directories = [
            base_path()    => '根目录',
            runtime_path() => '运行时目录',
            public_path()  => '公共目录'
        ];

        foreach ($directories as $dir => $name) {
            $writable = is_writable($dir);
            $checks[] = [
                'name'     => "{$name}权限",
                'required' => '可写',
                'current'  => $writable ? '可写' : '不可写',
                'status'   => $writable
            ];
        }

        // 检查必要文件
        $files = [
            base_path() . '/.env.example' => '.env.example文件',
            base_path() . '/mysql.sql' => '数据库文件'
        ];

        foreach ($files as $file => $name) {
            $exists   = file_exists($file);
            $checks[] = [
                'name'     => $name,
                'required' => '存在',
                'current'  => $exists ? '存在' : '不存在',
                'status'   => $exists
            ];
        }

        return $checks;
    }

    /**
     * 测试数据库连接
     */
    private function testDatabaseConnection(array $config): array
    {
        $host     = $config['db_host'];
        $port     = (int) ($config['db_port'] ?? 3306);
        $database = $config['db_name'];
        $username = $config['db_user'];
        $password = $config['db_password'] ?? '';

        // 先测试端口连通性
        if (!$this->testPortConnection($host, $port)) {
            throw new \Exception("无法连接到 {$host}:{$port}，请检查主机地址和端口是否正确");
        }

        // 强制指定端口，避免自动回退
        $dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";

        try {
            $pdo = new \PDO($dsn, $username, $password, [
                \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
                \PDO::ATTR_TIMEOUT            => 5, // 5秒超时
                \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
            ]);

            // 验证连接的端口是否正确
            $stmt       = $pdo->query("SELECT @@port as port");
            $result     = $stmt->fetch(\PDO::FETCH_ASSOC);
            $actualPort = (int) $result['port'];

            if ($actualPort !== $port) {
                $pdo = null;
                throw new \Exception("端口不匹配：期望端口 {$port}，实际连接端口 {$actualPort}");
            }

            // 检查数据库中是否有表
            $stmt       = $pdo->query("SHOW TABLES");
            $tables     = $stmt->fetchAll(\PDO::FETCH_COLUMN);
            $tableCount = count($tables);

            $pdo = null; // 关闭连接

            return [
                'hasData'    => $tableCount > 0,
                'tableCount' => $tableCount,
                'actualPort' => $actualPort
            ];
        } catch (\PDOException $e) {
            throw new \Exception('数据库连接失败: ' . $e->getMessage());
        }
    }

    /**
     * 测试端口连通性
     */
    private function testPortConnection(string $host, int $port): bool
    {
        $timeout = 3; // 3秒超时

        // 处理localhost的情况
        if ($host === 'localhost' || $host === '127.0.0.1') {
            $host = '127.0.0.1';
        }

        $connection = @fsockopen($host, $port, $errno, $errstr, $timeout);

        if ($connection) {
            fclose($connection);
            return true;
        }

        return false;
    }

    /**
     * 执行安装
     */
    private function performInstallation(array $config, Request $request): void
    {
        // 1. 创建.env文件
        $this->createEnvFile($config);

        // 2. 导入数据库
        $this->importDatabase($config);

        // 3. 创建管理员账户
        $this->createAdminUser($config);

        // 4. 配置站点域名
        $this->configureSiteDomain($request);
    }

    /**
     * 创建.env文件
     */
    private function createEnvFile(array $config): void
    {
        $envExample = base_path() . '/.env.example';
        $envFile    = base_path() . '/.env';

        if (!file_exists($envExample)) {
            throw new \Exception('.env.example文件不存在');
        }

        $env = file_get_contents($envExample);

        // 替换配置
        $env = preg_replace('/^DB_HOST\s*=.*$/m', 'DB_HOST = ' . $config['db_host'], $env);
        $env = preg_replace('/^DB_PORT\s*=.*$/m', 'DB_PORT = ' . ($config['db_port'] ?? 3306), $env);
        $env = preg_replace('/^DB_USERNAME\s*=.*$/m', 'DB_USERNAME = ' . $config['db_user'], $env);
        $env = preg_replace('/^DB_DATABASE\s*=.*$/m', 'DB_DATABASE = ' . $config['db_name'], $env);
        $env = preg_replace('/^DB_PASSWORD\s*=.*$/m', 'DB_PASSWORD = ' . $config['db_password'], $env);

        // 生成JWT密钥
        $jwtKey = Str::random(32);
        $env    = preg_replace('/^JWT_SECRET_KEY\s*=.*$/m', 'JWT_SECRET_KEY = ' . $jwtKey, $env);

        if (file_put_contents($envFile, $env) === false) {
            throw new \Exception('无法创建.env文件');
        }
    }

    /**
     * 导入数据库
     */
    private function importDatabase(array $config): void
    {
        $sqlFile = base_path() . '/mysql.sql';
        if (!file_exists($sqlFile)) {
            throw new \Exception('数据库文件不存在');
        }

        $host     = $config['db_host'];
        $port     = (int) ($config['db_port'] ?? 3306);
        $username = $config['db_user'];
        $password = $config['db_password'] ?? '';
        $database = $config['db_name'];

        // 再次验证端口连通性
        if (!$this->testPortConnection($host, $port)) {
            throw new \Exception("数据库导入失败：无法连接到 {$host}:{$port}");
        }

        $command = sprintf(
            'mysql -h%s -P%s -u%s %s %s < %s',
            escapeshellarg($host),
            escapeshellarg($port),
            escapeshellarg($username),
            $password ? '-p' . escapeshellarg($password) : '',
            escapeshellarg($database),
            escapeshellarg($sqlFile)
        );

        exec($command, $output, $returnVar);

        if ($returnVar !== 0) {
            throw new \Exception('数据库导入失败: ' . implode("\n", $output));
        }
    }

    /**
     * 修改管理员用户
     */
    private function createAdminUser(array $config): void
    {
        try {
            // 重新加载配置以使用新的数据库连接
            $this->reloadDatabaseConfig();

            // 检查管理员表是否存在
            $adminModel = new \app\common\model\AdminUser();

            // 直接修改ID为1的管理员用户
            $admin = $adminModel->find(1);
            if ($admin) {
                // 更新管理员信息
                $admin->username = $config['admin_username'];
                $admin->password = password_hash($config['admin_password'], PASSWORD_DEFAULT);
                if (!empty($config['admin_email'])) {
                    $admin->email = $config['admin_email'];
                }
                $admin->status      = 1;
                $admin->update_time = date('Y-m-d H:i:s');
                $admin->save();
            } else {
                throw new \Exception('未找到ID为1的管理员用户');
            }
        } catch (\Exception $e) {
            // 如果模型不存在或修改失败，记录日志但不中断安装
            error_log('修改管理员用户失败: ' . $e->getMessage());
        }
    }

    /**
     * 重新加载数据库配置
     */
    private function reloadDatabaseConfig(): void
    {
        // 清除配置缓存
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

        // 重新加载数据库配置
        $config = config('database');
        if ($config) {
            \think\facade\Db::setConfig($config);
        }
    }

    /**
     * 创建.lock文件
     */
    private function createLockFile(): void
    {
        $lockFile = base_path() . '/.lock';
        $content  = date('Y-m-d H:i:s');

        if (file_put_contents($lockFile, $content) === false) {
            throw new \Exception('无法创建.lock文件');
        }
    }

    /**
     * 配置站点域名
     */
    private function configureSiteDomain(Request $request): void
    {
        try {
            // 获取当前访问的完整URL
            $scheme = $request->header('x-forwarded-proto') ?: ($request->connection->transport === 'ssl' ? 'https' : 'http');
            $host   = $request->host();
            $port   = $request->connection->getRemotePort();

            // 从HTTP_HOST中提取端口信息
            $httpHost = $request->header('host');
            if ($httpHost && strpos($httpHost, ':') !== false) {
                list($host, $port) = explode(':', $httpHost, 2);
                $port              = (int) $port;
            } else {
                $port = ($scheme === 'https') ? 443 : 80;
            }

            // 构建站点域名
            $siteDomain = $scheme . '://' . $host;
            if (($scheme === 'http' && $port != 80) || ($scheme === 'https' && $port != 443)) {
                $siteDomain .= ':' . $port;
            }

            // 更新数据库配置
            $this->updateSystemConfig('site_domain', $siteDomain);
        } catch (\Exception $e) {
            error_log('配置站点域名失败: ' . $e->getMessage());
        }
    }

    /**
     * 更新系统配置
     */
    private function updateSystemConfig(string $name, string $value): void
    {
        try {
            $pdo  = $this->getDatabaseConnection();
            $stmt = $pdo->prepare("UPDATE system_config SET value = ? WHERE name = ?");
            $stmt->execute([$value, $name]);
        } catch (\Exception $e) {
            error_log('更新系统配置失败: ' . $e->getMessage());
        }
    }

    /**
     * 获取数据库连接
     */
    private function getDatabaseConnection(): \PDO
    {
        $envFile = base_path() . '/.env';
        if (!file_exists($envFile)) {
            throw new \Exception('.env文件不存在');
        }

        $env = file_get_contents($envFile);
        preg_match('/^DB_HOST\s*=\s*(.+)$/m', $env, $hostMatch);
        preg_match('/^DB_PORT\s*=\s*(.+)$/m', $env, $portMatch);
        preg_match('/^DB_DATABASE\s*=\s*(.+)$/m', $env, $dbMatch);
        preg_match('/^DB_USERNAME\s*=\s*(.+)$/m', $env, $userMatch);
        preg_match('/^DB_PASSWORD\s*=\s*(.*)$/m', $env, $passMatch);

        $host     = trim($hostMatch[1] ?? 'localhost');
        $port     = (int) trim($portMatch[1] ?? '3306');
        $database = trim($dbMatch[1] ?? '');
        $username = trim($userMatch[1] ?? '');
        $password = trim($passMatch[1] ?? '');

        // 验证端口连通性
        if (!$this->testPortConnection($host, $port)) {
            throw new \Exception("无法连接到数据库 {$host}:{$port}");
        }

        $dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";
        return new \PDO($dsn, $username, $password, [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_TIMEOUT => 5
        ]);
    }

    /**
     * 生成完成页面访问令牌
     */
    private function generateCompleteToken(): string
    {
        $token     = md5(uniqid() . time());
        $tokenFile = runtime_path() . '/install_complete_' . $token;
        file_put_contents($tokenFile, time());
        return $token;
    }

    /**
     * 验证完成页面令牌
     */
    private function validateCompleteToken(?string $token): bool
    {
        if (!$token) {
            return false;
        }

        $tokenFile = runtime_path() . '/install_complete_' . $token;
        if (!file_exists($tokenFile)) {
            return false;
        }

        // 检查令牌是否过期（5分钟）
        $createTime = (int) file_get_contents($tokenFile);
        return (time() - $createTime) < 300;
    }

    /**
     * 消费令牌（删除）
     */
    private function consumeCompleteToken(string $token): void
    {
        $tokenFile = runtime_path() . '/install_complete_' . $token;
        if (file_exists($tokenFile)) {
            unlink($tokenFile);
        }
    }

    /**
     * 重载服务
     */
    private function reloadService(): void
    {
        try {
            // 异步重载服务，避免阻塞当前请求
            exec('cd ' . base_path() . ' && php webman reload > /dev/null 2>&1 &');
        } catch (\Exception $e) {
            error_log('重载服务失败: ' . $e->getMessage());
        }
    }

    /**
     * 已安装时的重定向
     */
    private function redirectToInstalled(): Response
    {
        return response('
            <div style="text-align: center; padding: 50px; font-family: Arial, sans-serif;">
                <h1>系统已安装</h1>
                <p style="margin: 20px 0; color: #666;">系统已经安装完成，如需重复安装，请删除根目录 .lock 文件。</p>
                <p><a href="/" style="color: #007bff;">返回首页</a></p>
            </div>
        ', 403);
    }
}
